﻿#include "FITKGraphObjectInertia.h"

// VTK
#include <vtkPoints.h>
#include <vtkUnstructuredGrid.h>
#include <vtkCellData.h>
#include <vtkProperty.h>
#include <vtkIntArray.h>
#include <vtkSelectionNode.h>
#include <vtkSelection.h>
#include <vtkExtractSelection.h>
#include <vtkIdTypeArray.h>
#include <vtkIdList.h>
#include <vtkPointSource.h>
#include <vtkGlyph3D.h>

// For VTK 7
//@{
#include <vtkCellType.h>
//@}

// Graph
#include "FITK_Interface/FITKVTKAlgorithm/FITKGraphActor.h"
#include "FITKGraphCommons.h"

// Global data
#include "FITK_Kernel/FITKCore/FITKDataRepo.h"

// Data manager
#include "FITKSetSurfPolyProvider.h"

// Data
#include "FITK_Interface/FITKInterfaceModel/FITKAbstractAssInstance.h"
#include "FITK_Interface/FITKInterfaceStructural/FITKAbaAbstractInertia.h"

namespace Exchange
{
    FITKGraphObjectInertia::FITKGraphObjectInertia(AbaqusData::FITKDataCase* caseData, Interface::FITKAbaAbstractInertia* inertiaData)
        : FITKGraphObjectMarkBase(caseData, inertiaData)
    {
        if (!inertiaData)
        {
            return;
        }

        // Initialize.
        init();

        // Generate the inertia's surface or set grid data and the cell center of surface.
        bool flag = generatePolyData(inertiaData);
        if (!flag)
        {
            return;
        }

        // Generate actor.
        generateGraph();

        // Update the visibility.
        updateVisibility();
        
        // Save if the bounds are fixed or dynamic.
        m_hasFixedBounds = false;
    }

    FITKGraphObjectInertia::~FITKGraphObjectInertia()
    {
        // Delete the source.
        deleteVtkObj(m_pointSource);

        // Delete poly data.
        //@{
        deleteVtkObj(m_points);
        deleteVtkObj(m_ugrid);
        deleteVtkObj(m_modelIdArray);
        //@}

        // Delete extractor pointers.
        deleteVtkObj(m_selectNode);
        deleteVtkObj(m_section);
        deleteVtkObj(m_extractSelection);
        deleteVtkObj(m_idArray);

        // Clear the sub set ids.
        m_subSetIds.clear();
    }

    void FITKGraphObjectInertia::setVisible(bool visibility)
    {
        if (m_fActor)
        {
            m_fActor->SetVisibility(visibility);
        }
    }

    void FITKGraphObjectInertia::init()
    {
        // Create the source.
        m_pointSource = vtkPointSource::New();
        m_pointSource->SetRadius(0);
        m_pointSource->SetNumberOfPoints(1);

        // Create poly data.
        //@{
        m_ugrid = vtkUnstructuredGrid::New();
        m_points = vtkPoints::New();
        m_ugrid->SetPoints(m_points);
        m_modelIdArray = vtkIntArray::New();
        m_modelIdArray->SetNumberOfComponents(1);
        //@}

        // Initialize the extractor.
        //@{
        m_selectNode = vtkSelectionNode::New();
        m_section = vtkSelection::New();
        m_extractSelection = vtkExtractSelection::New();
        m_selectNode->SetContentType(vtkSelectionNode::INDICES);
        m_selectNode->SetFieldType(vtkSelectionNode::SelectionField::CELL);
        m_section->AddNode(m_selectNode);
        m_extractSelection->SetInputData(1, m_section);

        m_idArray = vtkIdTypeArray::New();
        m_selectNode->SetSelectionList(m_idArray);
        m_extractSelection->SetInputData(m_ugrid);
        //@}

        // Initialize the glyph size.
        m_glyphSize = 25.;
    }

    void FITKGraphObjectInertia::initActors()
    {
        // Get the color of inertia.
        double color3[3]{ 0., 0., 0. };
        FITKGraphCommons::QColorToDouble3(FITKGraphCommons::s_intertiaColor, color3);

        // Create the actor for glyph.
        m_fActor = FITKGraphActor::New();
        m_fActor->setGraphObject(this);
        m_fActor->SetPickable(false);
        m_fActor->SetVisibility(true);
        m_fActor->setScalarVisibility(false);
        m_fActor->GetProperty()->SetRepresentation(0);
        m_fActor->GetProperty()->SetColor(color3);
        m_fActor->GetProperty()->SetPointSize(m_glyphSize);
        addActor(m_fActor);
    }

    void FITKGraphObjectInertia::clearModelsInfo()
    {
        // Clear model ids.
        m_modelIds.clear();
        m_modelVisibilityHash.clear();
        m_subSetIds.clear();

        resetVtkObj(m_modelIdArray);
    }

    void FITKGraphObjectInertia::clearData()
    {
        // Clear data.
        resetVtkObj(m_points);
        resetVtkObj(m_ugrid);

        // Reset the component id.
        m_componentId = -1;

        // Clear the model infomation.
        clearModelsInfo();
    }

    void FITKGraphObjectInertia::highlight(QColor color)
    {
        // If the object is invisible, then return.
        if (!getDataVisibility())
        {
            return;
        }

        // Get the inertia highlight color.
        double color3[3] = { 0 ,0, 0 };
        if (color.isValid())
        {
            FITKGraphCommons::QColorToDouble3(color, color3);
        }
        else
        {
            FITKGraphCommons::QColorToDouble3(FITKGraphCommons::s_intertiaHighlightColor, color3);
        }      
        
        // Set the highlight color for lines.
        if (m_fActor)
        {
            m_fActor->GetProperty()->SetColor(color3);
        }

        FITKGraphObject3D::highlight();
    }

    void FITKGraphObjectInertia::disHighlight()
    {
        // Get the inertia default color.
        double color3[3] = { 0 ,0, 0 };
        FITKGraphCommons::QColorToDouble3(FITKGraphCommons::s_intertiaColor, color3);

        // Set the default color for lines.
        if (m_fActor)
        {
            m_fActor->GetProperty()->SetColor(color3);
        }     

        FITKGraphObject3D::disHighlight();
    }

    void FITKGraphObjectInertia::update(bool forceUpdate)
    {
        // Update poly data after the inertia's set or surface changed.
        Interface::FITKAbaAbstractInertia* inertiaData = dynamic_cast<Interface::FITKAbaAbstractInertia*>(_dataObj);
        if (!m_ugrid || !inertiaData)
        {
            return;
        }

        // Get the set( set or ref-node ) or surface id.    
        int compId = inertiaData->getComponentID();

        // Check if the component exists.
        bool isCompValid = getComponentValid(compId);

        bool needUpdate = false;
        if (!isCompValid)
        {
            needUpdate = true;

            // Clear data.
            clearData();
        }

        // Check if the locations changed.
        needUpdate |= (compId != m_componentId || forceUpdate);
        if (!needUpdate)
        {
            // Update the visibility only.
            updateVisibility();
            return;
        }

        // Clear data first.
        clearData();

        bool flag = generatePolyData(inertiaData);
        if (!flag)
        {
            return;
        }

        // Update the visibility at last.
        updateVisibility();

        // Modify actors.
        updateActors();
    }

    void FITKGraphObjectInertia::updateVisibility()
    {
        // Update the full actor.
        FITKGraphObject3D::updateVisibility();

        if (!m_idArray || !m_modelIdArray || !m_ugrid)
        {
            return;
        }

        QHash<int, bool> modelVisibilityHash;

        // Save the visibility first, and check if the visbility has changed at the same time.
        bool hasChanged = false;
        for (int modelId : m_modelIds)
        {
            bool parentVisible = getParentDataVisibility(modelId);
            bool oldVisibility = false;
            if (m_modelVisibilityHash.contains(modelId))
            {
                oldVisibility = m_modelVisibilityHash[modelId];
            }

            if (parentVisible != oldVisibility)
            {
                hasChanged = true;
            }

            if (!modelVisibilityHash.contains(modelId))
            {
                modelVisibilityHash.insert(modelId, parentVisible);
            }
        }

        // If the visibility hasn't changed, then return.
        if (!hasChanged && !m_modelVisibilityHash.isEmpty())
        {
            return;
        }

        // Reset the extractor's ids.
        resetVtkObj(m_idArray);

        // Update the extractor's ids.
        for (int i = 0; i < m_ugrid->GetNumberOfCells(); i++)
        {
            bool visibility = getCellVisibilityWithModel(m_ugrid->GetCell(i), m_modelIdArray, modelVisibilityHash);
            if (visibility)
            {
                m_idArray->InsertNextValue(i);
            }
        }

        // Update the data.
        m_ugrid->Modified();

        // Save the flags.
        m_modelVisibilityHash = modelVisibilityHash;
    }

    void FITKGraphObjectInertia::generateGraph()
    {
        if (!m_ugrid || !m_extractSelection)
        {
            return;
        }

        // Initialize the actors.
        initActors();

        // Create glyph filter.
        m_glyph = vtkGlyph3D::New();
        m_glyph->SetSourceConnection(m_pointSource->GetOutputPort());

        // Set the input data and connection.
        m_extractSelection->SetInputData(m_ugrid);
        m_glyph->SetInputConnection(m_extractSelection->GetOutputPort());      
        m_fActor->setInputConnection(m_glyph->GetOutputPort());
    }

    bool FITKGraphObjectInertia::generatePolyData(Interface::FITKBoundaryLocation* inertiaData)
    {
        if (!inertiaData || !m_ugrid)
        {
            return false;
        }

        // Get the component id.
        m_componentId = inertiaData->getComponentID();

        // Try to get set or surface poly data.
        QList<int> modelIds;
        QList<int> subSetIds;

        // Try to get the instance id.
        int instanceId = -1;

        Interface::FITKAbstractAssInstance* instance = inertiaData->getTModelObj<Interface::FITKAbstractAssInstance>();
        if (instance)
        {
            instanceId = instance->getDataObjectID();
        }

        // Generate component grid.
        vtkUnstructuredGrid* ugrid = vtkUnstructuredGrid::New();
        bool flag = generateComponentGridData(m_componentId, ugrid, modelIds, subSetIds, instanceId);
        if (!flag)
        {
            ugrid->Delete();
            return false;
        }

        // Save the model ids.
        m_modelIds << modelIds;

        // Save the sub set ids.
        m_subSetIds << subSetIds;

        // Add a point and a cell.
        auto addPoint = [&](double* position, int modelId)
        {
            // Add point.
            int ptId = m_points->InsertNextPoint(position);

            // Add cell.
            vtkSmartPointer<vtkIdList> cell = vtkSmartPointer<vtkIdList>::New();
            cell->InsertNextId(ptId);
            m_ugrid->InsertNextCell(VTKCellType::VTK_VERTEX, cell);

            // Add model id.
            m_modelIdArray->InsertNextValue(modelId);
        };

        // Add points of the cell center of component grid.
        //@{
        // Get the model id array of component.
        vtkIntArray* modelIdArray = vtkIntArray::SafeDownCast(ugrid->GetCellData()->GetArray(FITKSetSurfPolyProvider::getInstance()->getModelIdsArrayName()));

        for (int i = 0; i < ugrid->GetNumberOfCells(); i++)
        {
            int modelId = -1;
            if (modelIdArray)
            {
                modelId = modelIdArray->GetValue(i);
            }

            double pt[3]{ 0., 0., 0. };
            FITKGraphCommons::GetCellCenter(ugrid->GetCell(i), pt);
            addPoint(pt, modelId);
        }
        //@}

        ugrid->Delete();

        return flag;
    }

    bool FITKGraphObjectInertia::getDataVisibility()
    {
        bool isActive = false;

        // Try to down cast to inertia data.            
        Interface::FITKAbaAbstractInertia* inertia = dynamic_cast<Interface::FITKAbaAbstractInertia*>(_dataObj);
        if (inertia)
        {
            isActive = inertia->getActiveState();
        }
        
        return isActive;
    }

    void FITKGraphObjectInertia::simplified(bool enabled)
    {
        if (!m_graphSimplified)
        {
            return;
        }

        // Change the actor visibility while changing the camera.
        if (m_fActor && getDataVisibility())
        {
            m_fActor->SetVisibility(!enabled);
        }
    }

    bool FITKGraphObjectInertia::isRelatedTo(int dataObjId)
    {
        // Check if the id is the self-object id.
        bool isTheObj = FITKGraphObject3D::isRelatedTo(dataObjId);
        if (isTheObj)
        {
            return true;
        }

        // Check if the model ids contains the data id.
        bool hasModelRelated = m_modelIds.contains(dataObjId);
        if (hasModelRelated)
        {
            return true;
        }

        // Check if the location is the same as the data.
        bool hasCompRelated = m_componentId == dataObjId;
        if (hasCompRelated)
        {
            return true;
        }

        // Check if the data id is one of the sub set id.
        bool isSubSet = m_subSetIds.contains(dataObjId);
        if (isSubSet)
        {
            return true;
        }

        return false;
    }
}   // namespace Exchange